package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.catalina.Host;
import org.apache.lucene.search.join.ScoreMode;
import org.apache.lucene.util.QueryBuilder;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;


import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.security.Key;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ：Shine
 * @description：
 * @date ：2021/2/22 10:02
 */
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public void upperGoods(Long skuId) {
        Goods goods = new Goods();
        //向goods中添加数据
        //获取skuInfo信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        if (skuInfo != null) {
            goods.setId(skuInfo.getId());
            goods.setTitle(skuInfo.getSkuName());
            goods.setPrice(skuInfo.getPrice().doubleValue());
            goods.setCreateTime(new Date());
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());

            //赋值品牌信息
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            if (trademark != null) {
                goods.setTmId(trademark.getId());
                goods.setTmName(trademark.getTmName());
                goods.setTmLogoUrl(trademark.getLogoUrl());
            }

            //赋值分类信息
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            if ((categoryView != null)) {
                goods.setCategory1Id(categoryView.getCategory1Id());
                goods.setCategory1Name(categoryView.getCategory1Name());

                goods.setCategory2Id(categoryView.getCategory2Id());
                goods.setCategory2Name(categoryView.getCategory2Name());

                goods.setCategory3Id(categoryView.getCategory3Id());
                goods.setCategory3Name(categoryView.getCategory3Name());
            }

            //赋值平台属性
            List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
            if (!CollectionUtils.isEmpty(attrList)) {
                List<SearchAttr> searchAttrList = attrList.stream().map((baseAttrInfo -> {
                    SearchAttr searchAttr = new SearchAttr();
                    searchAttr.setAttrId(baseAttrInfo.getId());
                    searchAttr.setAttrName(baseAttrInfo.getAttrName());
                    searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                    return searchAttr;
                })).collect(Collectors.toList());

                //赋值平台属性集合
                goods.setAttrs(searchAttrList);
            }
        }

        //将信息保存到es中
        this.goodsRepository.save(goods);

    }

    @Override
    public void lowerGoods(Long skuId) {
        //删除es中的信息
        this.goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        //    定义Redis中的key
        String hotKey = "hotScore:";

        //    采用zset数据类型来存储数据，zset有序
        Double hotScore = redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);

        //    符合规则时，更新es
        if (hotScore % 10 == 0) {
            Optional<Goods> optional = this.goodsRepository.findById(skuId);
            Goods goods = optional.get();

            //修改热度值
            goods.setHotScore(Math.round(hotScore));

            // 保存到es
            this.goodsRepository.save(goods);
        }
    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        //java代码实现dsl语句
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);

        //使用restHC来查询数据，得到响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //将查询到的结果集封装到VO对象中
        SearchResponseVo searchResponseVo = this.parseSearchResult(searchResponse);

        //给部分属性赋值
        searchResponseVo.setPageSize(searchParam.getPageSize());
        searchResponseVo.setPageNo(searchParam.getPageNo());

        Long totalPages = (searchResponseVo.getTotal() + searchParam.getPageSize() - 1) / searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);


        return searchResponseVo;
    }


    /**
     * 动态生成Dsl语句
     *
     * @param searchParam
     * @return
     */
    private SearchRequest buildQueryDsl(SearchParam searchParam) {
        //构件查询器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //es中的语句：query----bool-----filter----term
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //判断用户根据那种条件进行检索
        //判断用户是否根据分类id进行过滤数据
        if (searchParam.getCategory1Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }
        if (searchParam.getCategory2Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }
        if (searchParam.getCategory3Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }

        //判断用户是否根据品牌过滤数据
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            //前端传过来的数据例：trademark=2:华为
            String[] split = trademark.split(":");
            if (split != null && split.length == 2) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", split[0]));
            }
        }

        //通过平台属性值进行过滤
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0) {
            for (String prop : props) {
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();

                //对字段进行分割
                String[] splits = prop.split(":");
                if (splits != null && splits.length == 3) {
                    //构件查询条件
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", splits[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", splits[1]));

                    //    将subQuery赋值到boolQuery
                    boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));

                    //    将boolQuery赋值给总的query
                    boolQueryBuilder.filter(boolQuery);
                }

            }
        }

        //通过关键字过滤数据
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));
        }

        //查询器调用query方法
        searchSourceBuilder.query(boolQueryBuilder);

        //查询器调用分页查询方法
        int from = (searchParam.getPageNo() - 1) * searchParam.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(searchParam.getPageSize());

        //查询器调用排序方法
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            String[] splits = order.split(":");
            if (splits != null && splits.length == 2) {
                //判断数组中第一位是什么，
                String field = "";
                switch (splits[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                //排序名称就是字段名称，排序方式使用三元表达式进行判断
                searchSourceBuilder.sort(field, "asc".equals(splits[1]) ? SortOrder.ASC : SortOrder.DESC);
            } else {
                //默认排序规则
                searchSourceBuilder.sort("hotScore", SortOrder.DESC);
            }

        }

        //查询器调用高亮方法，高亮的前提一定是先完成了查询
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        //查询器调用聚合,分别设置品牌聚合，设置品台属性聚合
        //关于品牌的聚合
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"));
        searchSourceBuilder.aggregation(termsAggregationBuilder);

        //关于品台属性的聚合，nested数据类型
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));

        //设置想要的字段，其他字段设置为null，在展示的时候更方便，需求字段有：id，defaultImg，title，price
        searchSourceBuilder.fetchSource(new String[]{"id", "defaultImg", "title", "price"}, null);

        //根据官网提示创建searchRequest对象,指定对goods库进行查询
        //ES中的查询语句：GET goods/info/_search
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.types("info");
        searchRequest.source(searchSourceBuilder);

        //searchSourceBuilder实际就是del语句
        System.out.println("DSL:\t" + searchSourceBuilder.toString());

        return searchRequest;
    }

    /**
     * 数据封装
     *
     * @param searchResponse
     * @return
     */
    private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        SearchHits hits = searchResponse.getHits();

        SearchHit[] subHits = hits.getHits();
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();

        //赋值品牌集合  trademarkList,通过aggregationMap的key获取到里面的值
        //此时将Aggregation转换为ParsedLongTerms类型，目的是为了拿到里面的buckets
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> searchResponseTmVoList = tmIdAgg.getBuckets().stream().map((bucket) -> {
            //声明一个品牌对象用来赋值
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();

            //此时得到了tm的Id
            String keyAsString = ((Terms.Bucket) bucket).getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(keyAsString));

            //赋值品牌Name,此时name是string类型，将aggregation转换为string类型
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //赋值品牌logoUrl
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);

            return searchResponseTmVo;
        }).collect(Collectors.toList());

        searchResponseVo.setTrademarkList(searchResponseTmVoList);

        //赋值商品属性集合  attrList,此处的attrAgg是nested类型
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map((bucket) -> {
            //声明一个品牌属性vo对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();

            Number keyAsNumber = ((Terms.Bucket) bucket).getKeyAsNumber();
            searchResponseAttrVo.setAttrId(keyAsNumber.longValue());

            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            //attrValue中有多个数据，需要循环遍历集合获取里面对应的每个数据
            List<String> attrValueList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(attrValueList);

            return searchResponseAttrVo;
        }).collect(Collectors.toList());

        searchResponseVo.setAttrsList(attrsList);

        //赋值商品集合 goodsList
        //subHits数组中存有多个goods此处需要new一个数组，来存储数据
        ArrayList<Goods> goodsList = new ArrayList<>();

        for (SearchHit subHit : subHits) {
            //此时得到了一个Goods.class类型的json数据
            String sourceAsString = subHit.getSourceAsString();

            Goods goods = JSON.parseObject(sourceAsString, Goods.class);

            //如果用户输入关键字检索，将获取高亮字段
            if (subHit.getHighlightFields().get("title") != null) {
                Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                //覆盖原来的title
                goods.setTitle(title.toString());
            }
            goodsList.add(goods);
        }

        searchResponseVo.setGoodsList(goodsList);

        //赋值total
        searchResponseVo.setTotal(hits.totalHits);


        return searchResponseVo;
    }
}
